﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Management;
using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
using System.Xml;

namespace Oxage.Printing
{
	public partial class XamlPrinter
	{
		#region Public properties
		/// <summary>
		/// Gets or sets a printer name to print with; null for the default printer.
		/// </summary>
		public string PrinterName
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a target width.
		/// </summary>
		public int? Width
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a target height.
		/// </summary>
		public int? Height
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a page orientation.
		/// </summary>
		public PageOrientation? Orientation
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a target margin.
		/// </summary>
		public Thickness Margin
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a stretch mode.
		/// </summary>
		public Stretch Stretch
		{
			get;
			set;
		}
		#endregion

		#region Constructor
		public XamlPrinter()
		{
		}
		#endregion

		#region Public methods
		public FlowDocument GetFlowDocument(FrameworkElement element)
		{
			if (element == null)
			{
				throw new ArgumentNullException("element", "FrameworkElement for printing cannot be null!");
			}

			VisualBrush brush = new VisualBrush(element);
			brush.AutoLayoutContent = true;
			brush.Stretch = this.Stretch;
			brush.TileMode = TileMode.None;

			return GetFlowDocument(brush);
		}

		public FlowDocument GetFlowDocument(VisualBrush brush)
		{
			if (brush == null)
			{
				throw new ArgumentNullException("brush", "VisualBrush for printing cannot be null!");
			}

			const double factor = 3.7795; //factor = 96 / 25.4; //dpi div inch-to-mm
			double w = this.Width.Value * factor;
			double h = this.Height.Value * factor;
			Thickness m = new Thickness(this.Margin.Left * factor, this.Margin.Top * factor, this.Margin.Right * factor, this.Margin.Bottom * factor);

			double width = w - m.Left - m.Right;
			double height = h - m.Top - m.Bottom;

			Grid container = new Grid();
			container.Background = brush;
			//container.Background = new SolidColorBrush(Colors.Pink); //Debug
			container.Width = width;
			container.Height = height;

			Paragraph p = new Paragraph();
			p.Inlines.Add(container);

			FlowDocument document = new FlowDocument();
			document.Blocks.Add(p);

			document.ColumnGap = 0;
			document.ColumnRuleWidth = 0;
			document.ColumnWidth = w;
			document.PageWidth = w;
			document.PageHeight = h;
			document.PagePadding = m;

			return document;
		}

		/// <summary>
		/// Prints the specified xaml file.
		/// </summary>
		/// <param name="source">Full path to the xaml file.</param>
		/// <param name="showDialog">A value indicating whether to show print dialog.</param>
		/// <returns>Returns true on success.</returns>
		public void Print(string sourceFilename, bool showDialog)
		{
			FrameworkElement element = null;
			using (XmlTextReader xmlReader = new XmlTextReader(sourceFilename))
			{
				//Load xaml element
				element = XamlReader.Load(xmlReader) as FrameworkElement;
			}
			Print(element, Path.GetFileName(sourceFilename), showDialog);
		}

		public void Print(FrameworkElement element, string description, bool showDialog)
		{
			FlowDocument document = GetFlowDocument(element);
			Print(document, description, showDialog);
		}

		public void Print(VisualBrush brush, string description, bool showDialog)
		{
			FlowDocument document = GetFlowDocument(brush);
			Print(document, description, showDialog);
		}

		public void Print(FlowDocument document, string description, bool showDialog)
		{
			IDocumentPaginatorSource flowDocument = document;
			PrintDialog flowPrintDialog = new PrintDialog();
			PrintQueue flowPrintQueue = flowPrintDialog.PrintQueue;

			const double factor = 3.7795; //factor = 96 / 25.4; //dpi div inch-to-mm
			double w = this.Width.Value * factor;
			double h = this.Height.Value * factor;

			//NOTE: System.Printing.PrintTicket requires ReachFramework
			flowPrintDialog.PrintTicket.PageMediaSize = new PageMediaSize(w, h);
			flowPrintDialog.PrintTicket.PageOrientation = this.Orientation;

			bool canPrint = false;
			if (showDialog)
			{
				//Show print dialog
				bool? confirm = flowPrintDialog.ShowDialog();
				canPrint = (confirm != null && confirm.Value);
			}
			else
			{
				//Print without dialog
				canPrint = true;
			}

			if (canPrint)
			{
				//Print document
				flowPrintDialog.PrintDocument(flowDocument.DocumentPaginator, description);
			}
		}
		#endregion

		#region Static methods
		/// <summary>
		/// Gets a default printer name.
		/// </summary>
		/// <returns></returns>
		public static string GetDefaultPrinterName()
		{
			//Reference: http://stackoverflow.com/questions/86138/whats-the-best-way-to-get-the-default-printer-in-net

			string result = null;
			var query = new ObjectQuery("SELECT * FROM Win32_Printer");
			var searcher = new ManagementObjectSearcher(query);
			var list = searcher.Get();

			foreach (ManagementObject item in list)
			{
				if (((bool?)item["Default"]) ?? false)
				{
					result = item["Name"] as string;
					break;
				}
			}

			return result;
		}

		/// <summary>
		/// Gets a list of installed printers.
		/// </summary>
		/// <returns></returns>
		public static List<string> GetInstalledPrinterNames()
		{
			List<string> result = new List<string>();
			var query = new ObjectQuery("SELECT * FROM Win32_Printer");
			var searcher = new ManagementObjectSearcher(query);
			var list = searcher.Get();

			foreach (ManagementObject item in list)
			{
				result.Add(item["Name"] as string);
			}

			return result;
		}
		#endregion
	}
}
